/**
 * @author qiao / https://github.com/qiao
 * @author mrdoob / http://mrdoob.com
 * @author alteredq / http://alteredqualia.com/
 * @author WestLangley / http://github.com/WestLangley
 */

requirejs(['js/three.min.js'], function() {
	THREE.OrbitControls = function(object, domElement) {

		this.object = object;
		this.domElement = (domElement !== undefined ) ? domElement : document;

		// API

		this.enabled = true;

		this.center = new THREE.Vector3();

		this.userZoom = true;
		this.userZoomSpeed = 1.0;

		this.userRotate = true;
		this.userRotateSpeed = 1.0;

		this.userPan = true;
		this.userPanSpeed = 2.0;

		this.autoRotate = false;
		this.autoRotateSpeed = 2.0;
		// 30 seconds per round when fps is 60

		this.minPolarAngle = 0;
		// radians
		this.maxPolarAngle = Math.PI;
		// radians

		this.minDistance = 0;
		this.maxDistance = Infinity;

		// 65 /*A*/, 83 /*S*/, 68 /*D*/
		this.keys = {
			LEFT : 37,
			UP : 38,
			RIGHT : 39,
			BOTTOM : 40,
			ROTATE : 65,
			ZOOM : 83,
			PAN : 68
		};

		// internals

		var scope = this;

		var EPS = 0.000001;
		var PIXELS_PER_ROUND = 1800;

		var rotateStart = new THREE.Vector2();
		var rotateEnd = new THREE.Vector2();
		var rotateDelta = new THREE.Vector2();

		var zoomStart = new THREE.Vector2();
		var zoomEnd = new THREE.Vector2();
		var zoomDelta = new THREE.Vector2();

		var phiDelta = 0;
		var thetaDelta = 0;
		var scale = 1;

		var lastPosition = new THREE.Vector3();

		var STATE = {
			NONE : -1,
			ROTATE : 0,
			ZOOM : 1,
			PAN : 2
		};
		var state = STATE.NONE;

		// events

		var changeEvent = {
			type : 'change'
		};

		this.rotateLeft = function(angle) {

			if (angle === undefined) {

				angle = getAutoRotationAngle();

			}

			thetaDelta -= angle;

		};

		this.rotateRight = function(angle) {

			if (angle === undefined) {

				angle = getAutoRotationAngle();

			}

			thetaDelta += angle;

		};

		this.rotateUp = function(angle) {

			if (angle === undefined) {

				angle = getAutoRotationAngle();

			}

			phiDelta -= angle;

		};

		this.rotateDown = function(angle) {

			if (angle === undefined) {

				angle = getAutoRotationAngle();

			}

			phiDelta += angle;

		};

		this.zoomIn = function(zoomScale) {

			if (zoomScale === undefined) {

				zoomScale = getZoomScale();

			}

			scale /= zoomScale;

		};

		this.zoomOut = function(zoomScale) {

			if (zoomScale === undefined) {

				zoomScale = getZoomScale();

			}

			scale *= zoomScale;

		};

		this.pan = function(distance) {

			distance.transformDirection(this.object.matrix);
			distance.multiplyScalar(scope.userPanSpeed);

			this.object.position.add(distance);
			this.center.add(distance);

		};

		this.update = function() {

			var position = this.object.position;
			var offset = position.clone().sub(this.center);

			// angle from z-axis around y-axis

			var theta = Math.atan2(offset.x, offset.z);

			// angle from y-axis

			var phi = Math.atan2(Math.sqrt(offset.x * offset.x + offset.z * offset.z), offset.y);

			if (this.autoRotate) {

				this.rotateLeft(getAutoRotationAngle());

			}

			theta += thetaDelta;
			phi += phiDelta;

			// restrict phi to be between desired limits
			phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, phi));

			// restrict phi to be betwee EPS and PI-EPS
			phi = Math.max(EPS, Math.min(Math.PI - EPS, phi));

			var radius = offset.length() * scale;

			// restrict radius to be between desired limits
			radius = Math.max(this.minDistance, Math.min(this.maxDistance, radius));

			offset.x = radius * Math.sin(phi) * Math.sin(theta);
			offset.y = radius * Math.cos(phi);
			offset.z = radius * Math.sin(phi) * Math.cos(theta);

			position.copy(this.center).add(offset);

			this.object.lookAt(this.center);

			thetaDelta = 0;
			phiDelta = 0;
			scale = 1;

			if (lastPosition.distanceTo(this.object.position) > 0) {

				this.dispatchEvent(changeEvent);

				lastPosition.copy(this.object.position);

			}

		};

		function getAutoRotationAngle() {

			return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;

		}

		function getZoomScale() {

			return Math.pow(0.95, scope.userZoomSpeed);

		}

		function onMouseDown(event) {

			if (scope.enabled === false)
				return;
			if (scope.userRotate === false)
				return;

			event.preventDefault();

			if (state === STATE.NONE) {
				if (event.button === 0)
					state = STATE.ROTATE;
				if (event.button === 1)
					state = STATE.ZOOM;
				if (event.button === 2)
					state = STATE.PAN;
			}

			if (state === STATE.ROTATE) {

				//state = STATE.ROTATE;

				rotateStart.set(event.clientX, event.clientY);

			} else if (state === STATE.ZOOM) {

				//state = STATE.ZOOM;

				zoomStart.set(event.clientX, event.clientY);

			} else if (state === STATE.PAN) {

				//state = STATE.PAN;

			}

			document.addEventListener('mousemove', onMouseMove, false);
			document.addEventListener('mouseup', onMouseUp, false);

		}

		function onMouseMove(event) {

			if (scope.enabled === false)
				return;

			event.preventDefault();

			if (state === STATE.ROTATE) {

				rotateEnd.set(event.clientX, event.clientY);
				rotateDelta.subVectors(rotateEnd, rotateStart);

				scope.rotateLeft(2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed);
				scope.rotateUp(2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed);

				rotateStart.copy(rotateEnd);

			} else if (state === STATE.ZOOM) {

				zoomEnd.set(event.clientX, event.clientY);
				zoomDelta.subVectors(zoomEnd, zoomStart);

				if (zoomDelta.y > 0) {

					scope.zoomIn();

				} else {

					scope.zoomOut();

				}

				zoomStart.copy(zoomEnd);

			} else if (state === STATE.PAN) {

				var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
				var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;

				scope.pan(new THREE.Vector3(-movementX, movementY, 0));

			}

		}

		function onMouseUp(event) {

			if (scope.enabled === false)
				return;
			if (scope.userRotate === false)
				return;

			document.removeEventListener('mousemove', onMouseMove, false);
			document.removeEventListener('mouseup', onMouseUp, false);

			state = STATE.NONE;

		}

		function onMouseWheel(event) {

			if (scope.enabled === false)
				return;
			if (scope.userZoom === false)
				return;

			var delta = 0;

			if (event.wheelDelta) {// WebKit / Opera / Explorer 9

				delta = event.wheelDelta;

			} else if (event.detail) {// Firefox

				delta = -event.detail;

			}

			if (delta > 0) {

				scope.zoomOut();

			} else {

				scope.zoomIn();

			}

		}

		function onKeyDown(event) {

			if (scope.enabled === false)
				return;
			if (scope.userPan === false)
				return;

			switch ( event.keyCode ) {

				/*case scope.keys.UP:
				 scope.pan( new THREE.Vector3( 0, 1, 0 ) );
				 break;
				 case scope.keys.BOTTOM:
				 scope.pan( new THREE.Vector3( 0, - 1, 0 ) );
				 break;
				 case scope.keys.LEFT:
				 scope.pan( new THREE.Vector3( - 1, 0, 0 ) );
				 break;
				 case scope.keys.RIGHT:
				 scope.pan( new THREE.Vector3( 1, 0, 0 ) );
				 break;
				 */
				case scope.keys.ROTATE:
					state = STATE.ROTATE;
					break;
				case scope.keys.ZOOM:
					state = STATE.ZOOM;
					break;
				case scope.keys.PAN:
					state = STATE.PAN;
					break;

			}

		}

		function onKeyUp(event) {

			switch ( event.keyCode ) {

				case scope.keys.ROTATE:
				case scope.keys.ZOOM:
				case scope.keys.PAN:
					state = STATE.NONE;
					break;
			}

		}


		this.domElement.addEventListener('contextmenu', function(event) {
			event.preventDefault();
		}, false);
		this.domElement.addEventListener('mousedown', onMouseDown, false);
		this.domElement.addEventListener('mousewheel', onMouseWheel, false);
		this.domElement.addEventListener('DOMMouseScroll', onMouseWheel, false);
		// firefox
		window.addEventListener('keydown', onKeyDown, false);
		window.addEventListener('keyup', onKeyUp, false);

	};

	THREE.OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype);
}); 